package org.qpcr.community.device.web;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.hswebframework.ezorm.rdb.exception.DuplicateKeyException;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.reactor.excel.ReactorExcel;
import org.hswebframework.web.api.crud.entity.PagerResult;
import org.hswebframework.web.api.crud.entity.QueryNoPagingOperation;
import org.hswebframework.web.api.crud.entity.QueryOperation;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Dimension;
import org.hswebframework.web.authorization.annotation.*;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.crud.query.QueryHelper;
import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.exception.NotFoundException;
import org.hswebframework.web.exception.ValidationException;
import org.hswebframework.web.i18n.LocaleUtils;
import org.hswebframework.web.id.IDGenerator;
import org.qpcr.community.PropertyMetric;
import org.qpcr.community.device.entity.*;
import org.qpcr.community.device.entity.vo.DeviceInstanceVo;
import org.qpcr.community.device.enums.DeviceState;
import org.qpcr.community.device.response.*;
import org.qpcr.community.device.service.DeviceConfigMetadataManager;
import org.qpcr.community.device.service.LocalDeviceInstanceService;
import org.qpcr.community.device.service.LocalDeviceProductService;
import org.qpcr.community.device.service.data.DeviceDataService;
import org.qpcr.community.device.service.data.DeviceProperties;
import org.qpcr.community.device.web.excel.DeviceExcelImporter;
import org.qpcr.community.device.web.excel.DeviceExcelInfo;
import org.qpcr.community.device.web.excel.DeviceWrapper;
import org.qpcr.community.device.web.excel.PropertyMetadataExcelInfo;
import org.qpcr.community.device.web.excel.PropertyMetadataWrapper;
import org.qpcr.community.device.web.excel.*;
import org.qpcr.community.device.web.request.AggRequest;
import org.qpcr.community.io.excel.AbstractImporter;
import org.qpcr.community.io.excel.ImportExportService;
import org.qpcr.community.io.file.FileManager;
import org.qpcr.community.io.utils.FileUtils;
import org.qpcr.community.relation.RelationObjectProvider;
import org.qpcr.community.relation.service.RelationService;
import org.qpcr.community.relation.service.request.SaveRelationRequest;
import org.qpcr.community.things.impl.metric.DefaultPropertyMetricManager;
import org.qpcr.community.timeseries.query.AggregationData;
import org.qpcr.community.web.response.ValidationResult;
import org.qpcr.core.Values;
import org.qpcr.core.device.*;
import org.qpcr.core.device.manager.DeviceBindHolder;
import org.qpcr.core.device.manager.DeviceBindProvider;
import org.qpcr.core.exception.DeviceOperationException;
import org.qpcr.core.message.DeviceMessage;
import org.qpcr.core.message.Message;
import org.qpcr.core.message.MessageType;
import org.qpcr.core.message.RepayableDeviceMessage;
import org.qpcr.core.metadata.*;
import org.qpcr.supports.official.TyLinksDeviceMetadataCodec;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.concurrent.Queues;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuple4;
import reactor.util.function.Tuples;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.hswebframework.reactor.excel.ReactorExcel.read;

@RestController
@RequestMapping({"/device-instance", "/device/instance"})
@Authorize
@Resource(id = "device-instance", name = "设备实例")
@Slf4j
@Tag(name = "设备实例接口")
public class DeviceInstanceController implements
        ReactiveServiceCrudController<DeviceInstanceEntity, String> {

    @Getter
    private final LocalDeviceInstanceService service;

    private final DeviceRegistry registry;

    private final LocalDeviceProductService productService;

    private final ImportExportService importExportService;

    private final ReactiveRepository<DeviceTagEntity, String> tagRepository;

    private final DeviceDataService deviceDataService;

    private final DeviceConfigMetadataManager metadataManager;

    private final RelationService relationService;

    private final TransactionalOperator transactionalOperator;

    private final FileManager fileManager;

    private final WebClient webClient;

    private final DeviceExcelFilterColumns filterColumns;

    private final DefaultPropertyMetricManager metricManager;

//    private final QueryHelper queryHelper;

    @SuppressWarnings("all")
    public DeviceInstanceController(LocalDeviceInstanceService service,
                                    DeviceRegistry registry,
                                    LocalDeviceProductService productService,
                                    ImportExportService importExportService,
                                    ReactiveRepository<DeviceTagEntity, String> tagRepository,
                                    DeviceDataService deviceDataService,
                                    DeviceConfigMetadataManager metadataManager,
                                    RelationService relationService,
                                    TransactionalOperator transactionalOperator,
                                    FileManager fileManager,
                                    WebClient.Builder builder,
                                    DeviceExcelFilterColumns filterColumns,
                                    DefaultPropertyMetricManager metricManager,QueryHelper queryHelper) {
        this.service = service;
        this.registry = registry;
        this.productService = productService;
        this.importExportService = importExportService;
        this.tagRepository = tagRepository;
        this.deviceDataService = deviceDataService;
        this.metadataManager = metadataManager;
        this.relationService = relationService;
        this.transactionalOperator = transactionalOperator;
        this.fileManager = fileManager;
        this.webClient = builder.build();
        this.filterColumns = filterColumns;
        this.metricManager = metricManager;
    }


    //获取设备详情
    @GetMapping("/{id:.+}/detail")
    @QueryAction
    @Operation(summary = "获取指定ID设备详情")
    public Mono<DeviceDetail> getDeviceDetailInfo(@PathVariable @Parameter(description = "设备ID") String id) {
        return service
                .getDeviceDetail(id)
                .switchIfEmpty(Mono.error(NotFoundException::new));
    }

    //读取设备属性
    @PostMapping("/{deviceId:.+}/properties/_read")
    @QueryAction
    @Operation(summary = "发送读取属性指令到设备", description = "请求示例: [\"属性ID\"]")
    public Mono<?> readProperties(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                  @RequestBody Mono<List<String>> properties) {
        return properties.flatMap(props -> service.readProperties(deviceId, props));
    }



    //获取设备详情
    @GetMapping("/{id:.+}/config-metadata")
    @QueryAction
    @Operation(summary = "获取设备需要的配置定义信息")
    public Flux<ConfigMetadata> getDeviceConfigMetadata(@PathVariable @Parameter(description = "设备ID") String id) {
        return metadataManager.getDeviceConfigMetadata(id);
    }


    @GetMapping("/{id:.+}/config-metadata/{metadataType}/{metadataId}/{typeId}")
    @QueryAction
    @Operation(summary = "获取设备物模型的拓展配置定义")
    public Flux<ConfigMetadata> getExpandsConfigMetadata(@PathVariable @Parameter(description = "设备ID") String id,
                                                         @PathVariable @Parameter(description = "物模型类型") DeviceMetadataType metadataType,
                                                         @PathVariable @Parameter(description = "物模型ID") String metadataId,
                                                         @PathVariable @Parameter(description = "类型ID") String typeId) {
        return service
                .findById(id)
                .flatMapMany(device -> metadataManager
                        .getMetadataExpandsConfig(device.getProductId(), metadataType, metadataId, typeId, DeviceConfigScope.device));
    }

    @GetMapping("/bind-providers")
    @QueryAction
    @Operation(summary = "获取支持的云云对接")
    public Flux<DeviceBindProvider> getBindProviders() {
        return Flux.fromIterable(DeviceBindHolder.getAllProvider());
    }


    //获取设备运行状态
    @GetMapping("/{id:.+}/state")
    @QueryAction
    @Operation(summary = "获取指定ID设备在线状态")
    public Mono<DeviceState> getDeviceState(@PathVariable @Parameter(description = "设备ID") String id) {
        return service.getDeviceState(id);
    }

    //激活设备
    @PostMapping("/{deviceId:.+}/deploy")
    @SaveAction
    @Operation(summary = "激活指定ID设备")
    public Mono<DeviceDeployResult> deviceDeploy(@PathVariable @Parameter(description = "设备ID") String deviceId) {
        return service.deploy(deviceId);
    }

    //重置配置信息
    @PutMapping("/{deviceId:.+}/configuration/_reset")
    @SaveAction
    @Operation(summary = "重置设备配置信息")
    public Mono<Map<String, Object>> resetConfiguration(@PathVariable @Parameter(description = "设备ID") String deviceId) {
        return service.resetConfiguration(deviceId);
    }

    @PutMapping("/configuration/_reset/ids")
    @SaveAction
    @Operation(summary = "重置设备配置信息(根据设备ID批量重置，性能欠佳，慎用)")
    public Mono<Long> resetConfigurationBatch(@RequestBody Flux<String> payload) {
        return service.resetConfiguration(payload);
    }

    @GetMapping(value = "/configuration/_reset/{productId:.+}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @ResourceAction(
            id = "batchResetConf",
            name = "批量重置设备配置信息"
    )
    @Operation(summary = "重置设备配置信息(根据产品批量重置，性能欠佳，慎用)")
    public Flux<ResetDeviceConfigurationResult> resetConfigurationBatch(@PathVariable @Parameter(description = "产品ID") String productId) {
        return service.resetConfigurationByProductId(productId);
    }

    //批量激活设备
    @GetMapping(value = "/deploy", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @SaveAction
    @QueryOperation(summary = "查询并批量激活设备")
    public Flux<DeviceDeployResult> deployAll(@Parameter(hidden = true) QueryParamEntity query) {
        query.setPaging(false);
        return service.query(query).as(service::deploy);
    }

    //取消激活
    @PostMapping("/{deviceId:.+}/undeploy")
    @SaveAction
    @Operation(summary = "注销指定ID的设备")
    public Mono<Integer> unDeploy(@PathVariable @Parameter(description = "设备ID") String deviceId) {
        return service.unregisterDevice(deviceId);
    }

    //断开连接
    @PostMapping("/{deviceId:.+}/disconnect")
    @SaveAction
    @Operation(summary = "断开指定ID的设备连接")
    public Mono<Boolean> disconnect(@PathVariable @Parameter(description = "设备ID") String deviceId) {
        return registry
                .getDevice(deviceId)
                .flatMapMany(DeviceOperator::disconnect)
                .singleOrEmpty();
    }

    //新建设备
    @PostMapping
    @Operation(summary = "新建设备")
    public Mono<DeviceInstanceEntity> add(@RequestBody Mono<DeviceInstanceEntity> payload) {
        return Mono
                .zip(payload, Authentication.currentReactive(), this::applyAuthentication)
                .flatMap(entity -> service.insert(Mono.just(entity)).thenReturn(entity))
                .onErrorMap(DuplicateKeyException.class, err -> new BusinessException("设备ID已存在", err));
    }

    /**
     * 同步设备真实状态
     *
     * @param query 过滤条件
     * @return 实时同步结果
     */
    @GetMapping(value = "/state/_sync", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @SaveAction
    @QueryNoPagingOperation(summary = "同步设备状态")
    public Flux<Integer> syncDeviceState(@Parameter(hidden = true) QueryParamEntity query) {
        query.setPaging(false);
        return service
                .query(query.includes("id"))
                .map(DeviceInstanceEntity::getId)
                .buffer(200)
                .publishOn(Schedulers.single())
                .concatMap(flux -> service.syncStateBatch(Flux.just(flux), true).map(List::size))
                .defaultIfEmpty(0);
    }

    //获取设备全部最新属性
    @GetMapping("/{deviceId:.+}/properties/latest")
    @QueryAction
    @Operation(summary = "获取指定ID设备最新的全部属性")
    public Flux<DeviceProperty> getDeviceLatestProperties(@PathVariable @Parameter(description = "设备ID") String deviceId) {
        return deviceDataService.queryEachOneProperties(deviceId, QueryParamEntity.of());
    }

    //获取设备全部最新属性
    @GetMapping("/{deviceId:.+}/properties")
    @QueryAction
    @QueryNoPagingOperation(summary = "按条件查询指定ID设备的全部属性")
    public Flux<DeviceProperty> getDeviceLatestProperties(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                          @Parameter(hidden = true) QueryParamEntity queryParamEntity) {
        return deviceDataService.queryEachProperties(deviceId, queryParamEntity);
    }

    //获取设备指定的最新属性
    @GetMapping("/{deviceId:.+}/property/{property:.+}")
    @QueryAction
    @Operation(summary = "获取指定ID设备最新的属性")
    public Mono<DeviceProperty> getDeviceLatestProperty(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                        @PathVariable @Parameter(description = "属性ID") String property) {
        return deviceDataService.queryEachOneProperties(deviceId, QueryParamEntity.of(), property)
                .take(1)
                .singleOrEmpty()
                ;
    }

    //查询属性列表
    @GetMapping("/{deviceId:.+}/property/{property}/_query")
    @QueryAction
    @QueryOperation(summary = "查询设备指定属性列表")
    public Mono<PagerResult<DeviceProperty>> queryDeviceProperties(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                                   @PathVariable @Parameter(description = "属性ID") String property,
                                                                   @Parameter(hidden = true) QueryParamEntity entity) {
        return deviceDataService.queryPropertyPage(deviceId, entity, property.split(","));
    }

    //查询属性列表
    @PostMapping("/{deviceId:.+}/property/{property}/_query")
    @QueryAction
    @Operation(summary = "(POST)查询设备指定属性列表")
    public Mono<PagerResult<DeviceProperty>> queryDeviceProperties(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                                   @PathVariable @Parameter(description = "属性ID") String property,
                                                                   @RequestBody Mono<QueryParamEntity> queryParam) {
        return queryParam.flatMap(param -> deviceDataService.queryPropertyPage(deviceId, param, property.split(",")));
    }

    @PostMapping("/{deviceId:.+}/property/{property}/_query/no-paging")
    @QueryAction
    @Operation(summary = "(POST)查询设备指定属性列表(不分页)")
    public Flux<DeviceProperty> queryDevicePropertiesNoPaging(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                              @PathVariable @Parameter(description = "属性ID") String property,
                                                              @RequestBody Mono<QueryParamEntity> queryParam) {
        return queryParam.flatMapMany(param -> deviceDataService.queryProperty(deviceId, param, property.split(",")));
    }

    //查询属性列表
    @GetMapping("/{deviceId:.+}/properties/_query")
    @QueryAction
    @QueryOperation(summary = "查询设备指定属性列表")
    @Deprecated
    public Mono<PagerResult<DeviceProperty>> queryDeviceProperties(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                                   @Parameter(hidden = true) QueryParamEntity entity ,String chatType) {
        return entity
                .getTerms()
                .stream()
                .filter(term -> "property".equals(term.getColumn()))
                .findFirst()
                .map(term -> {
                    String val = String.valueOf(term.getValue());
                    term.setValue(null);
                    return val;
                })
                .map(property -> deviceDataService.queryPropertyPage(deviceId, property, entity)
                        .map(page -> {
                            // 根据value字段中JSON字符串的count值去重
                            if (page != null && page.getData() != null && !page.getData().isEmpty()) {
                                Map<Object, DeviceProperty> uniqueMap = new java.util.LinkedHashMap<>();

                                for (DeviceProperty prop : page.getData()) {
                                    Object value = prop.getValue();
                                    if (value != null) {
                                        try {
                                            if (value instanceof String) {
                                                String jsonStr = (String) value;
                                                if (jsonStr.startsWith("{") && jsonStr.contains("count")) {
                                                    // 解析JSON字符串，提取count值
                                                    java.util.Map<String, Object> jsonObj = com.alibaba.fastjson.JSON.parseObject(jsonStr);
                                                    Object countValue = jsonObj.get("count");
                                                    if (countValue != null) {
                                                        // 如果count值已存在，保留最新的数据（时间戳更大的）
                                                        DeviceProperty existing = uniqueMap.get(countValue);
                                                        if (existing == null || prop.getTimestamp() > existing.getTimestamp()) {
                                                            uniqueMap.put(countValue, prop);
                                                        }
                                                        continue;
                                                    }
                                                }
                                            }
                                            // 如果不是JSON格式或没有count字段，直接添加
                                            uniqueMap.put(value, prop);
                                        } catch (Exception e) {
                                            // JSON解析失败，直接添加原始值
                                            uniqueMap.put(value, prop);
                                        }
                                    }
                                }

                                // 更新分页结果数据
                                page.setData(new java.util.ArrayList<>(uniqueMap.values()));
                            }
                            return page;
                        }))
                .orElseThrow(() -> new ValidationException("请设置[property]参数"));

    }
    //查询设备事件数据
    @GetMapping("/{deviceId:.+}/event/{eventId}")
    @QueryAction
    @QueryOperation(summary = "(GET)查询设备事件数据")
    public Mono<PagerResult<DeviceEvent>> queryPagerByDeviceEvent(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                                  @PathVariable @Parameter(description = "事件ID") String eventId,
                                                                  @Parameter(hidden = true) QueryParamEntity queryParam,
                                                                  @RequestParam(defaultValue = "false")
                                                                  @Parameter(description = "是否格式化返回结果,格式化对字段添加_format后缀") boolean format) {
        return deviceDataService.queryEventPage(deviceId, eventId, queryParam, format);
    }

    //查询设备事件数据
    @PostMapping("/{deviceId:.+}/event/{eventId}")
    @QueryAction
    @Operation(summary = "(POST)查询设备事件数据")
    public Mono<PagerResult<DeviceEvent>> queryPagerByDeviceEvent(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                                  @PathVariable @Parameter(description = "事件ID") String eventId,
                                                                  @RequestBody Mono<QueryParamEntity> queryParam,
                                                                  @RequestParam(defaultValue = "false")
                                                                  @Parameter(description = "是否格式化返回结果,格式化对字段添加_format后缀") boolean format) {
        return queryParam.flatMap(q -> deviceDataService.queryEventPage(deviceId, eventId, q, format));
    }



    //查询设备日志
    @GetMapping("/{deviceId:.+}/logs")
    @QueryAction
    @QueryOperation(summary = "(GET)查询设备日志数据")
    public Mono<PagerResult<DeviceOperationLogEntity>> queryDeviceLog(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                                      @Parameter(hidden = true) QueryParamEntity entity) {
        return deviceDataService.queryDeviceMessageLog(deviceId, entity);
    }

    //查询设备日志
    @PostMapping("/{deviceId:.+}/logs")
    @QueryAction
    @Operation(summary = "(POST)查询设备日志数据")
    public Mono<PagerResult<DeviceOperationLogEntity>> queryDeviceLog(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                                      @RequestBody @Parameter(hidden = true) Mono<QueryParamEntity> queryParam) {
        return queryParam.flatMap(param -> deviceDataService.queryDeviceMessageLog(deviceId, param));
    }


    //删除标签
    @DeleteMapping("/{deviceId}/tag/{tagId:.+}")
    @SaveAction
    @Operation(summary = "删除设备标签")
    public Mono<Void> deleteDeviceTag(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                      @PathVariable @Parameter(description = "标签ID") String tagId) {
        return tagRepository.createDelete()
                .where(DeviceTagEntity::getDeviceId, deviceId)
                .and(DeviceTagEntity::getId, tagId)
                .execute()
                .then();
    }

    /**
     * 批量删除设备,只会删除未激活的设备.
     *
     * @param idList ID列表
     * @return 被删除数量
     * @since 1.1
     */
    @PutMapping("/batch/_delete")
    @DeleteAction
    @Operation(summary = "批量删除设备")
    public Mono<Integer> deleteBatch(@RequestBody Mono<List<String>> idList) {
        return idList.flatMapMany(Flux::fromIterable)
                .as(service::deleteById);
    }

    /**
     * 批量注销设备
     *
     * @param idList ID列表
     * @return 被注销的数量
     * @since 1.1
     */
    @PutMapping("/batch/_unDeploy")
    @SaveAction
    @Operation(summary = "批量注销设备")
    public Mono<Integer> unDeployBatch(@RequestBody Mono<List<String>> idList) {
        return idList.flatMap(list -> service.unregisterDevice(Flux.fromIterable(list)));
    }

    /**
     * 批量激活设备
     *
     * @param idList ID列表
     * @return 被激活的数量
     */
    @PutMapping("/batch/_deploy")
    @SaveAction
    @Operation(summary = "批量激活设备")
    public Mono<Integer> deployBatch(@RequestBody Mono<List<String>> idList) {
        return idList.flatMapMany(service::findById)
                .as(service::deploy)
                .map(DeviceDeployResult::getTotal)
                .reduce(Math::addExact);
    }

    /**
     * 获取设备全部标签
     * <pre>
     *     GET /device/instance/{deviceId}/tags
     *
     *     [
     *      {
     *          "id":"id",
     *          "key":"",
     *          "value":"",
     *          "name":""
     *      }
     *     ]
     * </pre>
     *
     * @param deviceId 设备ID
     * @return 设备标签列表
     */
    @GetMapping("/{deviceId}/tags")
    @SaveAction
    @Operation(summary = "获取设备全部标签数据")
    public Flux<DeviceTagEntity> getDeviceTags(@PathVariable @Parameter(description = "设备ID") String deviceId) {
        return tagRepository.createQuery()
                .where(DeviceTagEntity::getDeviceId, deviceId)
                .fetch();
    }

    //保存设备标签
    @PatchMapping("/{deviceId}/tag")
    @SaveAction
    @Operation(summary = "保存设备标签")
    public Flux<DeviceTagEntity> saveDeviceTag(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                               @RequestBody Flux<DeviceTagEntity> tags) {
        return tags
                .doOnNext(tag -> {
                    tag.setId(DeviceTagEntity.createTagId(deviceId, tag.getKey()));
                    tag.setDeviceId(deviceId);
                    tag.tryValidate();
                })
                .as(tagRepository::save)
                .thenMany(getDeviceTags(deviceId));
    }

    DataBufferFactory bufferFactory = new DefaultDataBufferFactory();

    private Mono<Tuple4<DeviceProductEntity, DeviceProductOperator, DeviceMetadata, List<ConfigPropertyMetadata>>> getDeviceProductDetail(String productId) {

        return Mono.zip(
                //产品
                productService.findById(productId),
                //操作接口
                registry.getProduct(productId),
                //物模型
                registry.getProduct(productId).flatMap(DeviceProductOperator::getMetadata),
                //配置
                metadataManager.getDeviceConfigMetadataByProductId(productId)
                        .flatMapIterable(ConfigMetadata::getProperties)
                        .collectList()
                        .defaultIfEmpty(Collections.emptyList())
        );
    }

    //按产品导入数据
    @GetMapping(value = "/{productId}/import", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @SaveAction
    @Operation(summary = "导入设备数据")
    public Flux<ImportDeviceInstanceResult> doBatchImportByProduct(@PathVariable @Parameter(description = "产品ID") String productId,
                                                                   @RequestParam(defaultValue = "false") @Parameter(description = "自动启用") boolean autoDeploy,
                                                                   @RequestParam(required = false) @Parameter(description = "文件地址,支持csv,xlsx文件格式") String fileUrl,
                                                                   @RequestParam(required = false) @Parameter(description = "文件Id") String fileId,
                                                                   @RequestParam(defaultValue = "32") @Parameter int speed) {
        return Authentication
                .currentReactive()
                .flatMapMany(auth -> {

                    //从当前用户的维度中获取机构信息,需要将用户绑定到对应到机构.
                    Map<String, String> orgMapping = auth
                            .getDimensions("org")
                            .stream()
                            .collect(Collectors.toMap(Dimension::getName, Dimension::getId, (_1, _2) -> _1));

                    return this
                            .getDeviceProductDetail(productId)
                            .map(tp4 -> Tuples.of(new DeviceWrapper(tp4.getT3().getTags(), tp4.getT4()), tp4.getT1()))
                            .flatMapMany(wrapper -> importExportService
                                    .readData(fileUrl, fileId, wrapper.getT1())
                                    .doOnNext(info -> info.setProductName(wrapper.getT2().getName()))
                            )
                            .map(info -> {
                                DeviceInstanceEntity entity = FastBeanCopier.copy(info, new DeviceInstanceEntity());
                                entity.setProductId(productId);
                                entity.setOrgId(orgMapping.get(info.getOrgName()));
                                if (StringUtils.isEmpty(entity.getId())) {
                                    throw new BusinessException("第" + (info.getRowNumber() + 1) + "行:设备ID不能为空");
                                }
                                return Tuples.of(entity, info.getTags());
                            })
                            .as(flux -> handleImportDevice(flux, autoDeploy, speed));
                });
    }

    @GetMapping(value = "/{productId}/import/_withlog", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @SaveAction
    @Operation(summary = "导入设备数据，并提供日志下载")
    public Flux<ImportDeviceInstanceResult> doBatchImportByProductWithLog(
            @PathVariable @Parameter(description = "产品ID") String productId,
            @RequestParam(defaultValue = "false") @Parameter(description = "自动启用") boolean autoDeploy,
            @RequestParam @Parameter(description = "文件地址,支持csv,xlsx文件格式") String fileUrl,
            @RequestParam(defaultValue = "32") @Parameter int speed
    ) {
        return Authentication
                .currentReactive()
                .flatMapMany(auth -> this
                        .getDeviceProductDetail(productId)
                        .map(tp4 -> new DeviceExcelImporter(fileManager, webClient, tp4.getT1(), tp4.getT4(), auth))
                        .flatMapMany(importer -> importer
                                .doImport(fileUrl)
                                .groupBy(
                                        result -> result.isSuccess() && result.getType() == AbstractImporter.ImportResultType.data,
                                        Integer.MAX_VALUE
                                )
                                .flatMap(group -> {
                                    // 处理导入成功的设备
                                    if (group.key()) {
                                        return group
                                                .map(result -> Tuples.of(result.getData().getDevice(), result.getData().getTags()))
                                                .as(flux -> handleImportDevice(flux, autoDeploy, speed));
                                    }
                                    // 返回错误信息和导入结果详情文件地址
                                    return group
                                            .map(result -> {
                                                ImportDeviceInstanceResult response = new ImportDeviceInstanceResult();
                                                response.setSuccess(result.isSuccess());
                                                if (StringUtils.hasText(result.getMessage())) {
                                                    response.setMessage(String.format("第%d行：%s", result.getRow(), result.getMessage()));
                                                }
                                                response.setDetailFile(result.getDetailFile());
                                                return response;
                                            });
                                })
                        )
                );
    }

    private Flux<ImportDeviceInstanceResult> handleImportDevice(Flux<Tuple2<DeviceInstanceEntity, List<DeviceTagEntity>>> flux,
                                                                boolean autoDeploy,
                                                                int speed) {
        return flux
                .buffer(100)//每100条数据保存一次
                .map(Flux::fromIterable)
                .flatMap(buffer -> Mono
                                .zip(buffer
                                                .map(Tuple2::getT1)
                                                .as(service::save)
                                                .flatMap(res -> {
                                                    if (autoDeploy) {
                                                        return service
                                                                .deploy(buffer.map(Tuple2::getT1))
                                                                .then(Mono.just(res));
                                                    }
                                                    return Mono.just(res);
                                                }),
                                        tagRepository
                                                .save(buffer.flatMapIterable(Tuple2::getT2))
                                                .defaultIfEmpty(SaveResult.of(0, 0)))
                                .as(transactionalOperator::transactional),
                        Math.min(speed, Queues.XS_BUFFER_SIZE))
                .map(res -> ImportDeviceInstanceResult.success(res.getT1()))
                .onErrorResume(err -> Mono.just(ImportDeviceInstanceResult.error(err)));
    }

    //获取导出模版
    @GetMapping("/{productId}/template.{format}")
    @QueryAction
    @Operation(summary = "下载设备导入模版")
    public Mono<Void> downloadExportTemplate(@PathVariable @Parameter(description = "产品ID") String productId,
                                             ServerHttpResponse response,
                                             @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("设备导入模版." + format, StandardCharsets.UTF_8
                        .displayName())));
        return getDeviceProductDetail(productId)
                .map(tp4 -> DeviceExcelInfo.getTemplateHeaderMapping(filterColumns,tp4.getT3().getTags(), tp4.getT4()))
                .defaultIfEmpty(DeviceExcelInfo.getTemplateHeaderMapping(filterColumns,Collections.emptyList(), Collections.emptyList()))
                .flatMapMany(headers ->
                        ReactorExcel.<DeviceExcelInfo>writer(format)
                                .headers(headers)
                                .converter(DeviceExcelInfo::toMap)
                                .writeBuffer(Flux.empty()))
                .doOnError(err -> log.error(err.getMessage(), err))
                .map(bufferFactory::wrap)
                .as(response::writeWith);
    }

    //按照型号导出数据.
    @GetMapping("/{productId}/export.{format}")
    @QueryAction
    @QueryNoPagingOperation(summary = "按产品导出设备实例数据")
    public Mono<Void> export(@PathVariable @Parameter(description = "产品ID") String productId,
                             ServerHttpResponse response,
                             @Parameter(hidden = true) QueryParamEntity parameter,
                             @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("设备实例." + format, StandardCharsets.UTF_8
                        .displayName())));
        parameter.setPaging(false);
        parameter.toNestQuery(q -> q.is(DeviceInstanceEntity::getProductId, productId));
        return Authentication
                .currentReactive()
                .flatMap(auth -> {
                    //从当前用户的维度中获取机构信息,需要将用户绑定到对应到机构.
                    Map<String, String> orgMapping = auth
                            .getDimensions("org")
                            .stream()
                            .collect(Collectors.toMap(Dimension::getId, Dimension::getName, (_1, _2) -> _1));
                    return this
                            .getDeviceProductDetail(productId)
                            .map(tp4 -> Tuples
                                    .of(
                                            //表头
                                            DeviceExcelInfo.getExportHeaderMapping(filterColumns,tp4.getT3().getTags(), tp4.getT4()),
                                            //配置key集合
                                            tp4
                                                    .getT4()
                                                    .stream()
                                                    .map(ConfigPropertyMetadata::getProperty)
                                                    .collect(Collectors.toList())
                                    ))
                            .defaultIfEmpty(Tuples.of(DeviceExcelInfo.getExportHeaderMapping(filterColumns,Collections.emptyList(), Collections
                                            .emptyList()),
                                    Collections.emptyList()))
                            .flatMapMany(headerAndConfigKey -> ReactorExcel
                                    .<DeviceExcelInfo>writer(format)
                                    .headers(headerAndConfigKey.getT1())
                                    .converter(DeviceExcelInfo::toMap)
                                    .writeBuffer(service
                                                    .query(parameter)
                                                    .flatMap(entity -> {
                                                        DeviceExcelInfo exportEntity = FastBeanCopier.copy(entity, new DeviceExcelInfo(), "state");
                                                        exportEntity.setOrgName(orgMapping.get(entity.getOrgId()));
                                                        exportEntity.setState(entity.getState().getText());
                                                        return registry
                                                                .getDevice(entity.getId())
                                                                .flatMap(deviceOperator -> deviceOperator
                                                                        .getSelfConfigs(headerAndConfigKey.getT2())
                                                                        .map(Values::getAllValues))
                                                                .doOnNext(configs -> exportEntity
                                                                        .getConfiguration()
                                                                        .putAll(configs))
                                                                .thenReturn(exportEntity);
                                                    })
                                                    .buffer(200)
                                                    .flatMap(list -> {
                                                        Map<String, DeviceExcelInfo> importInfo = list
                                                                .stream()
                                                                .collect(Collectors.toMap(DeviceExcelInfo::getId, Function.identity()));
                                                        return tagRepository
                                                                .createQuery()
                                                                .where()
                                                                .in(DeviceTagEntity::getDeviceId, importInfo.keySet())
                                                                .fetch()
                                                                .collect(Collectors.groupingBy(DeviceTagEntity::getDeviceId))
                                                                .flatMapIterable(Map::entrySet)
                                                                .doOnNext(entry -> importInfo
                                                                        .get(entry.getKey())
                                                                        .setTags(entry.getValue()))
                                                                .thenMany(Flux.fromIterable(list));
                                                    })
                                            , 512 * 1024))//缓冲512k
                            .doOnError(err -> log.error(err.getMessage(), err))
                            .map(bufferFactory::wrap)
                            .as(response::writeWith);
                });
    }


    //直接导出数据,不支持导出标签.
    @GetMapping("/export.{format}")
    @QueryAction
    @QueryNoPagingOperation(summary = "导出设备实例数据", description = "此操作不支持导出设备标签和配置信息")
    public Mono<Void> export(ServerHttpResponse response,
                             @Parameter(hidden = true) QueryParamEntity parameter,
                             @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("设备实例." + format, StandardCharsets.UTF_8
                        .displayName())));
        return ReactorExcel.<DeviceExcelInfo>writer(format)
                .headers(DeviceExcelInfo.getExportHeaderMapping(filterColumns,Collections.emptyList(), Collections.emptyList()))
                .converter(DeviceExcelInfo::toMap)
                .writeBuffer(
                        service
                                .query(parameter)
                                .map(entity -> {
                                    DeviceExcelInfo exportEntity = FastBeanCopier.copy(entity, new DeviceExcelInfo(), "state");
                                    exportEntity.setState(entity.getState().getText());
                                    return exportEntity;
                                })
                        , 512 * 1024)//缓冲512k
                .doOnError(err -> log.error(err.getMessage(), err))
                .map(bufferFactory::wrap)
                .as(response::writeWith);
    }

    //设置设备影子
    @PutMapping("/{deviceId:.+}/shadow")
    @SaveAction
    @Operation(summary = "设置设备影子")
    public Mono<String> setDeviceShadow(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                        @RequestBody Mono<String> shadow) {
        return Mono
                .zip(registry.getDevice(deviceId), shadow)
                .flatMap(tp2 -> tp2.getT1()
                        .setConfig(DeviceConfigKey.shadow, tp2.getT2())
                        .thenReturn(tp2.getT2()));
    }

    //获取设备影子
    @GetMapping("/{deviceId:.+}/shadow")
    @SaveAction
    @Operation(summary = "获取设备影子")
    public Mono<String> getDeviceShadow(@PathVariable @Parameter(description = "设备ID") String deviceId) {
        return registry
                .getDevice(deviceId)
                .flatMap(operator -> operator.getSelfConfig(DeviceConfigKey.shadow))
                .defaultIfEmpty("{\n}");
    }

    //设置设备属性
    @PutMapping("/{deviceId:.+}/property")
    @SneakyThrows
    @QueryAction
    @Operation(summary = "发送设置属性指令到设备", description = "请求示例: {\"属性ID\":\"值\"}")
    public Flux<?> writeProperties(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                   @RequestBody Mono<Map<String, Object>> properties) {
        return properties.flatMapMany(props -> service.writeProperties(deviceId, props));
    }

    //设备功能调用
    @PostMapping("/{deviceId:.+}/function/{functionId}")
    @SneakyThrows
    @QueryAction
    @Operation(summary = "发送调用设备功能指令到设备", description = "请求示例: {\"参数\":\"值\"}")
    public Flux<?> invokedFunction(@PathVariable String deviceId,
                                   @PathVariable String functionId,
                                   @RequestBody Mono<Map<String, Object>> properties) {

        return properties.flatMapMany(props -> service.invokeFunction(deviceId, functionId, props));
    }

    @PostMapping("/{deviceId:.+}/agg/_query")
    @QueryAction
    @Operation(summary = "聚合查询设备属性")
    public Flux<Map<String, Object>> aggDeviceProperty(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                       @RequestBody Mono<AggRequest> param) {

        return param
                .flatMapMany(request -> deviceDataService
                        .aggregationPropertiesByDevice(deviceId,
                                request.getQuery(),
                                request
                                        .getColumns()
                                        .toArray(new DeviceDataService.DevicePropertyAggregation[0]))
                )
                .map(AggregationData::values);
    }

    //查询属性列表
    @PostMapping("/{deviceId:.+}/properties/_query/no-paging")
    @QueryAction
    @Operation(summary = "不分页查询设备的全部属性(一个属性为一列)",
            description = "设备使用列式存储模式才支持")
    public Flux<DeviceProperties> queryDevicePropertiesNoPaging(@PathVariable
                                                                @Parameter(description = "设备ID") String deviceId,
                                                                @RequestBody Mono<QueryParamEntity> entity) {
        return entity.flatMapMany(q -> deviceDataService.queryProperties(deviceId, q));
    }

    //发送设备指令
    @PostMapping("/{deviceId:.+}/message")
    @SneakyThrows
    @QueryAction
    @Operation(summary = "发送指令到设备")
    @SuppressWarnings("all")
    public Flux<?> sendMessage(@PathVariable @Parameter(description = "设备ID") String deviceId,
                               @RequestBody Mono<Map<String, Object>> properties) {
        return properties
                .flatMapMany(props -> {
                    return Mono
                            .zip(
                                    registry
                                            .getDevice(deviceId)
                                            .map(DeviceOperator::messageSender)
                                            .switchIfEmpty(Mono.error(() -> new NotFoundException("设备不存在或未激活"))),
                                    Mono.<Message>justOrEmpty(MessageType.convertMessage(props))
                                            .cast(DeviceMessage.class)
                                            .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("不支持的消息格式")))
                            ).flatMapMany(tp2 -> {
                                DeviceMessageSender sender = tp2.getT1();
                                DeviceMessage message = tp2.getT2();

                                Map<String, String> copy = new HashMap<>();
                                copy.put("deviceId", deviceId);
                                if (!StringUtils.hasText(message.getMessageId())) {
                                    copy.put("messageId", IDGenerator.SNOW_FLAKE_STRING.generate());
                                }
                                FastBeanCopier.copy(copy, message);
                                return sender
                                        .send(message)
                                        .onErrorResume(DeviceOperationException.class, error -> {
                                            if (message instanceof RepayableDeviceMessage) {
                                                return Mono.just(
                                                        ((RepayableDeviceMessage) message).newReply().error(error)
                                                );
                                            }
                                            return Mono.error(error);
                                        });
                            });
                });
    }

    //发送设备指令
    @PostMapping("/messages")
    @SneakyThrows
    @QueryAction
    @Operation(summary = "批量发送指令到设备")
    @SuppressWarnings("all")
    public Flux<?> sendMessage(@RequestParam(required = false)
                               @Parameter(description = "按查询条件发送指令") String where,
                               @RequestBody Flux<Map<String, Object>> messages) {

        Lazy<Flux<DeviceOperator>> operators = Lazy.of(() -> {
            if (StringUtils.isEmpty(where)) {
                throw new ValidationException("where", "[where]参数不能为空");
            }
            QueryParamEntity entity = new QueryParamEntity();
            entity.setWhere(where);
            entity.includes("id");
            return service.query(entity)
                    .flatMap(device -> registry.getDevice(device.getId()))
                    .cache();
        });
        return messages
                .flatMap(message -> {
                    DeviceMessage msg = MessageType
                            .convertMessage(message)
                            .filter(DeviceMessage.class::isInstance)
                            .map(DeviceMessage.class::cast)
                            .orElseThrow(() -> new UnsupportedOperationException("不支持的消息格式:" + message));

                    String deviceId = msg.getDeviceId();
                    Flux<DeviceOperator> devices = StringUtils.isEmpty(deviceId)
                            ? operators.get()
                            : registry.getDevice(deviceId).flux();

                    return devices
                            .flatMap(device -> {
                                Map<String, Object> copy = new HashMap<>(message);
                                copy.put("deviceId", device.getDeviceId());
                                copy.putIfAbsent("messageId", IDGenerator.SNOW_FLAKE_STRING.generate());
                                //复制为新的消息,防止冲突
                                DeviceMessage copiedMessage = MessageType
                                        .convertMessage(copy)
                                        .map(DeviceMessage.class::cast)
                                        .orElseThrow(() -> new UnsupportedOperationException("不支持的消息格式"));
                                return device
                                        .messageSender()
                                        .send(copiedMessage)
                                        .onErrorResume(Exception.class, error -> {
                                            if (copiedMessage instanceof RepayableDeviceMessage) {
                                                return Mono.just(
                                                        ((RepayableDeviceMessage) copiedMessage).newReply().error(error)
                                                );
                                            }
                                            return Mono.error(error);
                                        });
                            });
                });
    }

    //更新设备物模型
    @PutMapping(value = "/{id}/metadata")
    @SaveAction
    @Operation(summary = "更新物模型")
    public Mono<Void> updateMetadata(@PathVariable String id,
                                     @RequestBody Mono<String> metadata) {
        return metadata
                .flatMap(metadata_ -> service
                        .createUpdate()
                        .set(DeviceInstanceEntity::getDeriveMetadata, metadata_)
                        .where(DeviceInstanceEntity::getId, id)
                        .execute()
                        .then(registry.getDevice(id))
                        .flatMap(device -> device.updateMetadata(metadata_)))
                .then();
    }

    //重置设备物模型
    @DeleteMapping(value = "/{id}/metadata")
    @SaveAction
    @Operation(summary = "重置物模型")
    public Mono<Void> resetMetadata(@PathVariable String id) {

        return registry
                .getDevice(id)
                .flatMap(DeviceOperator::resetMetadata)
                .then(service
                        .createUpdate()
                        .setNull(DeviceInstanceEntity::getDeriveMetadata)
                        .where(DeviceInstanceEntity::getId, id)
                        .execute()
                        .then());
    }

    //合并产品的物模型
    @PutMapping(value = "/{id}/metadata/merge-product")
    @SaveAction
    @Operation(summary = "合并产品的物模型")
    public Mono<Void> mergeProductMetadata(@PathVariable String id) {
        return service
                .findById(id)
                //只有单独保存过物模型的才合并
                .filter(deviceInstance -> StringUtils.hasText(deviceInstance.getDeriveMetadata()))
                .flatMap(deviceInstance -> productService
                        .findById(deviceInstance.getProductId())
                        .flatMap(product -> deviceInstance.mergeMetadata(product.getMetadata()))
                        .then(
                                Mono.defer(() -> service
                                        .createUpdate()
                                        .set(deviceInstance::getDeriveMetadata)
                                        .where(deviceInstance::getId)
                                        .execute()
                                        .then(registry.getDevice(deviceInstance.getId()))
                                        .flatMap(device -> device.updateMetadata(deviceInstance.getDeriveMetadata()))
                                        .then())
                        ));
    }


    @GetMapping("/{id:.+}/exists")
    @QueryAction
    @Operation(summary = "验证设备ID是否存在")
    public Mono<Boolean> deviceIdValidate(@PathVariable @Parameter(description = "设备ID") String id) {
        return service.findById(id)
                .hasElement();
    }

    @GetMapping("/id/_validate")
    @QueryAction
    @Operation(summary = "验证设备ID是否合法")
    public Mono<ValidationResult> deviceIdValidate2(@RequestParam @Parameter(description = "设备ID") String id) {
        return LocaleUtils.currentReactive()
                .flatMap(locale -> {
                    DeviceInstanceEntity entity = new DeviceInstanceEntity();
                    entity.setId(id);
                    entity.validateId();

                    return service.findById(id)
                            .map(device -> ValidationResult.error(
                                    LocaleUtils.resolveMessage("error.device_ID_already_exists", locale)))
                            .defaultIfEmpty(ValidationResult.success());
                })
                .onErrorResume(ValidationException.class, e -> Mono.just(e.getI18nCode())
                        .map(ValidationResult::error));
    }


    //解析文件为属性物模型
    @PostMapping(value = "/{productId}/property-metadata/import")
    @SaveAction
    @Operation(summary = "解析文件为属性物模型")
    public Mono<String> importPropertyMetadata(@PathVariable @Parameter(description = "产品ID") String productId,
                                               @RequestParam @Parameter(description = "文件地址,支持csv,xlsx文件格式") String fileUrl) {
        return metadataManager
                .getMetadataExpandsConfig(productId, DeviceMetadataType.property, "*", "*", DeviceConfigScope.device)
                .collectList()
                .map(PropertyMetadataWrapper::new)
                //解析数据并转为物模型
                .flatMap(wrapper -> importExportService
                        .getInputStream(fileUrl)
                        .flatMapMany(inputStream -> read(inputStream, FileUtils.getExtension(fileUrl), wrapper))
                        .map(PropertyMetadataExcelInfo::toMetadata)
                        .collectList())
                .filter(CollectionUtils::isNotEmpty)
                .map(list -> {
                    SimpleDeviceMetadata metadata = new SimpleDeviceMetadata();
                    list.forEach(metadata::addProperty);
                    return TyLinksDeviceMetadataCodec.getInstance().doEncode(metadata);
                });
    }

    //获取物模型属性导入模块
    @GetMapping("/{deviceId}/property-metadata/template.{format}")
    @QueryAction
    @Operation(summary = "下载设备物模型属性导入模块")
    public Mono<Void> downloadMetadataExportTemplate(@PathVariable @Parameter(description = "设备ID") String deviceId,
                                                     ServerHttpResponse response,
                                                     @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("物模型导入模块." + format, StandardCharsets.UTF_8
                        .displayName())));

        return metadataManager
                .getMetadataExpandsConfig(deviceId, DeviceMetadataType.property, "*", "*", DeviceConfigScope.device)
                .collectList()
                .map(PropertyMetadataExcelInfo::getTemplateHeaderMapping)
                .flatMapMany(headers ->
                        ReactorExcel.<DeviceExcelInfo>writer(format)
                                .headers(headers)
                                .converter(DeviceExcelInfo::toMap)
                                .writeBuffer(Flux.empty()))
                .doOnError(err -> log.error(err.getMessage(), err))
                .map(bufferFactory::wrap)
                .as(response::writeWith)
                ;
    }


    @PatchMapping("/{deviceId}/relations")
    @Operation(summary = "保存设备的关系信息")
    @SaveAction
    public Mono<Void> saveRelation(@PathVariable String deviceId,
                                   @RequestBody Flux<SaveRelationRequest> requestFlux) {
        return relationService.saveRelated(RelationObjectProvider.TYPE_DEVICE, deviceId, requestFlux);
    }


    @PatchMapping("/{deviceId}/metric/property/{property}")
    @Operation(summary = "保存设备属性指标数据")
    @SaveAction
    public Mono<Void> savePropertyMetric(@PathVariable String deviceId,
                                         @PathVariable String property,
                                         @RequestBody Flux<PropertyMetric> metricFlux) {
        return metricManager
                .savePropertyMetrics(DeviceThingType.device.getId(),
                        deviceId,
                        property,
                        metricFlux)
                .then();
    }

    @GetMapping("/{deviceId}/metric/property/{property}")
    @Operation(summary = "获取设备属性指标数据")
    @SaveAction
    public Flux<PropertyMetric> getPropertyMetric(@PathVariable String deviceId,
                                                  @PathVariable String property) {
        return metricManager
                .getPropertyMetrics(DeviceThingType.device.getId(), deviceId, property);
    }

    @PostMapping("/update-state/has-sync/{devicedId:.+}")
    @QueryAction
    @Operation(summary = "更新设备同步状态为已同步")
    @Authorize(ignore = true)
    public Mono<Integer> updateStateHasSync(@PathVariable String devicedId) {
        return service.updateStateHasSync(Collections.singletonList(devicedId));
    }


    /**
     * type 用户登入后查看所属设备（0 可链接  1不可链接）用户端接口
     */
    @GetMapping("/queryDeviceByDept")
//    @Authorize(ignore = true)
    @Operation(summary = "用户登入后查看所属设备（0 可链接  1不可链接）")
    public Mono<PagerResult<DeviceInstanceEntity>> queryDeviceByDept(@Parameter String deptId, @Parameter Integer type,@Parameter Integer index,@Parameter Integer pageSize) {

//        return   this.service.queryDeviceByDept(deptId,type,index,pageSize);
        return   this.service.queryDeviceByUser(type,index,pageSize);
    }

    /**
     * 已连接设备数
     * @param deptId
     * @return
     */
    @GetMapping("/connectDeviceCount")
    @Operation(summary = "已连接设备数")
    public Mono<Integer> connectDeviceCount(){
        return   this.service.connectDeviceCount();
    }


    /**
     *
     * @param id 设备ID
     * @param type 0 断开链接  1建立连接
     * @return
     */
    @PutMapping("updateState")
    @SaveAction
    @Operation(summary = "用户登录后与设备进行绑定")
    public Mono<Integer> updateState(@Parameter String id, @Parameter Integer type  ) {

        return   this.service.updateDeviceState(id,type,0);

    }

    /**
     * 设备登录后账号与设备进行绑定
     * @param id
     * @param type
     * @param account
     * @return
     */
    @PostMapping("deviceUpdateState")
    @SaveAction
    @Authorize(ignore = true)
    @Operation(summary = "设备登录后账号与设备进行绑定")
    public Mono<Integer> deviceUpdateState(@Parameter String id, @Parameter Integer type , @Parameter String account ) {
        log.info("设备修改绑定状态,ID:{}  账号:{}  类型:{}",id,account,type);
        return   this.service.deviceUpdateState(id,type,account);

    }


    @GetMapping("queryDeviceByUserName")
    @Operation(summary = "查询当前登入账号链接的设备")
    public Mono<DeviceInstanceEntity> queryDeviceByUserName( ) {

        return   this.service.queryDeviceByUserName();

    }

    @GetMapping("queryDeviceById/{id}")
    @Operation(summary = "查询设备基本信息")
    public Mono<DeviceInstanceEntity> queryDeviceById(@PathVariable String id ) {

        return   this.service.queryDeviceById(id);

    }

    /**
     * 查询单位已经绑定的设备列表【后端绑定用】
     */
    @GetMapping("queryDeviceRelate")
    @Operation(summary = "查询单位已经绑定的设备列表【后端绑定用】")
    public Mono<PagerResult<DeviceDeptRelateResponse>> queryDeviceRelate(@Parameter String deptId,@Parameter Integer index,@Parameter Integer pageSize ) {

        return   this.service.queryDeviceRelate(deptId,index,pageSize);

    }
    /**
     * 根据用户部门查询设备列表(普通用户使用)
     */
    @GetMapping("/queryDeviceListByDeptId")
    @Operation(summary = "根据用户部门查询设备列表")
    public Mono<PagerResult<DeviceInstanceVo>> queryDeviceByWhere(@Parameter String id,@Parameter String deptId,@Parameter String productId,@Parameter Integer pageIndex,@Parameter Integer pageSize,@Parameter String state,@Parameter String name) {

        return   this.service.queryDeviceByWhere(id,deptId,productId,pageIndex,pageSize,state,name);
    }




    /**
     * 查询单位还未绑定的设备列表【后端绑定用】
     */
    @GetMapping("/queryDeviceNoRelate")
    @Operation(summary = "查询单位还未绑定的设备列表【后端绑定用】")
    public Mono<PagerResult<DeviceInstanceEntity>> queryDeviceNoRelate(@Parameter Integer index,@Parameter Integer pageSize) {

        return   this.service.queryDeviceNoRelate(index,pageSize);
    }


    @RequestMapping("_query-pager-with-product")
    public Mono<PagerResult<DeviceInstanceVo>> queryPagerWithProduct(@RequestBody QueryParamEntity query) {
        return this.service.queryPagerWithProduct(query);
    }
}
